/// Pin functions.
#define PrynInternal
#define PrynImplementation
#include <pryn.h>
#include "strings.h"

static const PrynValue nullValue = { 0, 0 };
static const PrynString nullString = { 0, 0 };

#define PRYN_PIN_LIST(PIN) ((PIN)->pDirection == PrynPinDirectionOutput ? &(PIN)->pComponent->pOutputPins : &(PIN)->pComponent->pInputPins)
#define PRYN_PIN_STATE(PIN) ((PIN)->pComponent->pFactory->pLibrary->pState)

#define PrynCheckPin() prynAssert (pin, NullArgument); state = PRYN_PIN_STATE (pin)

// Build a function for a pin, ensuring it's non-zero before executing body, and assigns state.
#define PrynPinBody(BODY) \
	do { \
		PrynStartFunction (); \
		{ \
			PrynCheckPin (); \
			BODY \
		} \
		PrynEndFunction (); \
	} while (0)

// Completely define a function for retrieving a pin attribute. NAME is like State for prynPinState, TYPE is the type of property to retrieve, INITIAL is the clear value of the parameter, STORE stores the value in *output.
#define PrynPinFunctionGetPropertyBase(NAME, TYPE, INITIAL, STORE) \
	PrynExport (PrynResult, prynPin##NAME, (PrynPin *pin, TYPE *output)) \
	{ \
		if (output) *output = INITIAL; \
		PrynPinBody ({ \
			prynAssert (output, NullArgument); \
			STORE \
		}); \
	}

// Simplified for normal properties.
#define PrynPinFunctionGetProperty(NAME, TYPE, INITIAL) PrynPinFunctionGetPropertyBase (NAME, TYPE, INITIAL, { *output = pin->p##NAME; })

// For PrynString properties, which have an unusual initialiser.
#define PrynPinFunctionGetPropertyString(NAME) PrynPinFunctionGetPropertyBase (NAME, PrynString, nullString, { *output = pin->p##NAME; })

PrynPinFunctionGetPropertyBase (State, PrynState *, 0, { if (output) *output = state; })
PrynPinFunctionGetPropertyBase (Tags, PrynTagList *, 0, { if (output) *output = &pin->pCommonObject.pTags; })
PrynPinFunctionGetProperty (Connected, PrynPin *, 0)
PrynPinFunctionGetProperty (Component, PrynComponent *, 0)
PrynPinFunctionGetPropertyBase (Value, PrynValue *, 0, { if (output) *output = pin->pValue; })
PrynPinFunctionGetPropertyBase (Next, PrynPin *, 0, { if (output) *output = pin->pNext; prynReturnBoolean (pin->pNext); })
PrynPinFunctionGetPropertyBase (Previous, PrynPin *, 0, { if (output) *output = pin->pPrevious; prynReturnBoolean (pin->pPrevious); })
PrynPinFunctionGetPropertyBase (List, PrynPinList *, 0, { *output = PRYN_PIN_LIST (pin); })

PrynExport (PrynResult, prynPinIsConnected, (PrynPin *pin)) { PrynPinBody ({ prynReturnBoolean (pin->pConnected); });  }
PrynExport (PrynResult, prynPinIsOutput, (PrynPin *pin)) { PrynPinBody ({ prynReturnBoolean (pin->pDirection == PrynPinDirectionOutput); }); }
PrynExport (PrynResult, prynPinIsInput, (PrynPin *pin)) { PrynPinBody ({ prynReturnBoolean (pin->pDirection == PrynPinDirectionInput); }); }
PrynExport (PrynResult, prynPinIsLast, (PrynPin *pin)) { PrynPinBody ({ prynReturnBoolean (!pin->pNext); }); }
PrynExport (PrynResult, prynPinIsFirst, (PrynPin *pin)) { PrynPinBody ({ prynReturnBoolean (!pin->pPrevious); }); }

PrynExport (PrynResult, prynPinCreate, (PrynPin **output, PrynComponent *component, PrynPinDirection direction, const PrynString *id, const PrynString *name, const PrynString *description))
{
	PrynStartFunction ();
	PrynPin *pin = 0;
	PrynPinList *list;
	
	if (output) *output = 0;
	prynAssert (component, NullArgument);
	state = component->pFactory->pLibrary->pState;
	prynAssert (id && name && description, NullArgument);
	prynAssert (direction == PrynPinDirectionOutput || direction == PrynPinDirectionInput, BadArgument);
	prynCall (prynAllocateClearedObject (state, &pin));

	pin->pCommonObject.pType = PrynObjectTypePin;
	pin->pDirection = direction;
	pin->pComponent = component;
	list = PRYN_PIN_LIST (pin);
	prynDListAppendP (*list, pin);
	
	pin->pId = *id;
	pin->pCommonResource.pName = *name;
	pin->pCommonResource.pDescription = *description;
	*output = pin;
	
	PrynEndFunction ();
}

PrynExport (PrynResult, prynPinCreateOutput, (PrynPin **pin, PrynComponent *component, const PrynString *id, const PrynString *name, const PrynString *description))
{
	return prynPinCreate (pin, component, PrynPinDirectionOutput, id, name, description);
}

PrynExport (PrynResult, prynPinCreateInput, (PrynPin **pin, PrynComponent *component, const PrynString *id, const PrynString *name, const PrynString *description))
{
	return prynPinCreate (pin, component, PrynPinDirectionInput, id, name, description);
}

PrynExport (PrynResult, prynPinDestroy, (PrynPin *pin))
{
	PrynPinList *list;
	PrynState *state;

	if (!pin) return PrynResultNullArgument;
	list = PRYN_PIN_LIST (pin);
	state = PRYN_PIN_STATE (pin);
	prynCommonResourceDestroy (&pin->pCommonResource, state);
	prynDListRemoveP (*list, pin);
	return prynFree (state, pin);
}

PrynExport (PrynResult, prynPinListCount, (PrynPinList *list, size_t *output))
{
	PrynPin *item;
	size_t count = 0;

	if (output) *output = 0;
	if (!list || !output) return PrynResultNullArgument;

	prynDListEachP (item, *list)
		count ++;
	*output = count;
	return PrynResultSuccess;
}

PrynExport (PrynResult, prynPinSetValue, (PrynPin *pin, PrynValue *value))
{
	PrynStartFunction ();
	PrynFactory *factory = 0;
	PrynPin *connected = 0;

	PrynCheckPin ();
	factory = pin->pComponent->pFactory;
	connected = pin->pConnected;

	if (pin->pDirection == PrynPinDirectionOutput)
	{
		PrynResult otherResult = PrynResultSuccess;

		prynValueDestroy (pin->pValue);

		pin->pValue = value;
		if (connected) connected->pValue = value;

		result = prynPinChangeValue (pin);
		if (connected) otherResult = prynPinChangeValue (connected);

		if (result >= 0 && otherResult < 0) result = otherResult;
		if (result < 0) goto failure;
	}
	else if (connected)
		return prynPinSetValue (connected, value);
	PrynEndFunction ();
}

PrynExport (PrynResult, prynPinChangeValue, (PrynPin *pin))
{
	PrynFactoryPinChangeValueFunction function;

	PrynPinBody ({
		PrynResult other = PrynResultSuccess;
		PrynFactory *factory = pin->pComponent->pFactory;
		PrynObject object;

		if ((function = factory->pPinChangeValueFunction) != 0)
			result = (*function) (pin, factory->pPinChangeValueFunctionData);

		factory = pin->pConnected ? pin->pConnected->pComponent->pFactory : 0;
		if (factory && (function = factory->pPinChangeValueFunction) != 0)
			other = (*function) (pin->pConnected, factory->pPinChangeValueFunctionData);

		if (result >= 0 && other < 0)
		{
			result = other;
			goto failure;
		}

		prynPinToObject (pin, &object);
		prynChangeDispatchSimple (PrynChangeTypePinChangeValue, &object);
	});
}

PrynExport (PrynResult, prynPinDisconnect, (PrynPin *pin))
{
	PrynStartFunction ();
	PrynPin *other = 0;
	PrynFactory *factory = 0;
	PrynFactory *otherFactory;

	PrynCheckPin ();
	other = pin->pConnected;
	factory = pin->pComponent->pFactory;

	if (!other) prynReturnDone ();
	otherFactory = other->pComponent->pFactory;

	other->pConnected = 0;
	pin->pConnected = 0;
	factory->pPinDisconnectedFunction (pin, factory->pPinDisconnectedFunctionData);
	otherFactory->pPinDisconnectedFunction (other, otherFactory->pPinDisconnectedFunctionData);

	prynPinSetValue (pin->pDirection == PrynPinDirectionInput ? pin : other, 0);
	PrynEndFunction ();
}

PrynExport (PrynResult, prynPinCanConnect, (PrynPin *from, PrynPin *to))
{
	PrynStartFunction ();
	
	if (from) prynPinState (from, &state);
	else if (to) prynPinState (to, &state);

	prynAssert (from && to && (from != to), False);
	if (from->pDirection == PrynPinDirectionInput) prynSwap (PrynPin *, from, to);
	prynAssert (from->pDirection == PrynPinDirectionOutput && to->pDirection == PrynPinDirectionInput, False);

	PrynEndFunction ();
}

PrynExport (PrynResult, prynPinConnect, (PrynPin *from, PrynPin *to))
{
	PrynStartFunction ();
	PrynFactory *fromFactory, *toFactory;
	
	if (from) prynPinState (from, &state);
	else if (to) prynPinState (to, &state);
	
	prynCall (prynPinCanConnect (from, to));
	prynAssert (PrynIsTrue (result), BadArgument);
	
	if (from->pDirection == PrynPinDirectionInput) prynSwap (PrynPin *, from, to);

	fromFactory = from->pComponent->pFactory;
	toFactory = to->pComponent->pFactory;

	prynPinDisconnect (from);
	prynPinDisconnect (to);

	from->pConnected = to;
	to->pConnected = to;

	prynValueDestroy (to->pValue);
	to->pValue = from->pValue;
	prynPinChangeValue (to);

	fromFactory->pPinConnectedFunction (from, fromFactory->pPinConnectedFunctionData);
	toFactory->pPinConnectedFunction (to, toFactory->pPinConnectedFunctionData);

	PrynEndFunction ();
}

